/* This file is part of FireEscape.
 *
 * FireEscape is the intellectual work of Phillip Cheng & Allen Park.
 * All future releases or modifications of this program must include this
 * attribuation as according to the Creative Commons BY-SA license.
 *
 * FireEscape is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * FireEscape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FireEscape.  If not, see <http://www.gnu.org/licenses/>.
 *
 * All modifications to this program should include this original statement.
 *
 * If you modify, distribute, or use the program , please contact
 * fireescape@kloudvine.com
 * http://code.google.com/p/fireescape/
 *
 * If you are interested in using FireEscape for your structure, please contact us
 * for support.
 *
 * If you do use FireEscape, please consider donating to the project.
 */
package fireescape;

import java.awt.Frame;
import java.awt.Graphics2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 * This is the class used by clients to interact with the server
 * @author drtechmaster
 */
public class Client {

    public static final short PORT = 4444;
    public static ArrayList<String> addresses = new ArrayList<String>();
    public static BufferedReader kIn = null;
    private static InetSocketAddress server;
    private static int timeoutMs;
    private static Socket clientSocket;
    private static String clientID;
    private static int bench;
    private static final String loggingServerListAddress = "http://www.kloudvine.com/loggingServers.txt";
    private static final String fireEscapeServers = "http://www.kloudvine.com/FireEscapeServers.txt";
    private static final String onlineStatusServers = "http://www.kloudvine.com/onlineStatus.txt";
    public static boolean communicating;

    /**
     * Initializes the client
     */
    public static void initialize() {
        //applet dialog pane
        communicating = true;
        //TODO THIS LINE FOR LOCAL RUNS ONLY
        addresses.add("localhost");
        /*try {
            // get  server address
            URL kloudvine = new URL(fireEscapeServers);
            URLConnection kloudvineConnect = kloudvine.openConnection();
            kIn = new BufferedReader(new InputStreamReader(kloudvineConnect.getInputStream()));
            String inputLine;

            while ((inputLine = kIn.readLine()) != null) {
                addresses.add(inputLine);
            }
            kIn.close();
        } catch (MalformedURLException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex1) {
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
            }
        } catch (IOException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

            try {
                Thread.sleep(2000);
            } catch (InterruptedException ex1) {
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
            }

        } finally {
        }*/
        communicating = false;
        createServerID();
        bench = benchmark(4000);

    }

    /**
     * Returns a genome
     * @return a genome
     */
    public static synchronized Genome getGenome() {
        communicating = true;
        //System.out.println("getGenome method");
        int i = 0;
        Boolean notFound = true;


        while (notFound) {
            checkOnlineStatus();
            try {
                Boolean gotData = false;
                clientSocket = new Socket();
                //test log server and get working server
                InetAddress addr = InetAddress.getByName(addresses.get(i));
                server = new InetSocketAddress(addr, PORT);
                // Create an unbound socket
                // This method will block no more than timeoutMs.
                // If the timeout occurs, SocketTimeoutException is thrown.
                timeoutMs = 2000;   // 2 seconds
                clientSocket.connect(server, timeoutMs);
                ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream());
                out.writeObject(new Packet(clientID, clientSocket.getLocalAddress().toString(), "getGenome", bench));
                ObjectInputStream in = new ObjectInputStream(clientSocket.getInputStream());
                Packet packet = (Packet) in.readObject();
                out.flush();
                in.close();
                out.close();
                clientSocket.close();
                communicating = false;
                return packet.getGenome();
            } catch (SocketTimeoutException ex) {
                i++;
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex1) {
                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
                }
            } catch (UnknownHostException ex) {
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex1) {
                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
                }
            } catch (ConnectException ex) {
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

            } catch (IOException ex) {
                ex.printStackTrace();

            } catch (IndexOutOfBoundsException ex) {
                System.err.println("Fire Escape Servers offline.");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

                i = 0;
            } catch (ClassNotFoundException ex) {
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, "Server malfunction - server sent a funky class");
                System.exit(-1);
            }
        }
        return null;
    }

    /**
     * The client returns the genome to the server
     * @param genome The genome to return
     */
    public static synchronized void returnGenome(Genome genome) {
        communicating = true;
        //System.out.println("returnGenome method 123");
        int i = 0;
        //System.out.println(i);
        Boolean notFound = true;
        //System.out.println(notFound);

        checkOnlineStatus();

        while (notFound) {
            checkOnlineStatus();
            //System.out.println("in while");
            try {
                //System.out.println("intry");
                clientSocket = new Socket();
                //test log server and get working server
                //System.out.println(addresses.get(i));
                InetAddress addr = InetAddress.getByName(addresses.get(i));
                server = new InetSocketAddress(addr, PORT);
                // Create an unbound socket
                // This method will block no more than timeoutMs.
                // If the timeout occurs, SocketTimeoutException is thrown.
                timeoutMs = 2000;   // 2 seconds
                clientSocket.connect(server, timeoutMs);
                ObjectOutputStream out = new ObjectOutputStream(clientSocket.getOutputStream());
                out.writeObject(new Packet(clientID, clientSocket.getLocalAddress().toString(), "returnGenome", genome));
                //TODO Doesn't seem to work correctly.
                File file = new File("scoresheet.txt");
                System.out.println(file.getCanonicalPath());
                if (file.canWrite()) {
                    BufferedWriter scores = new BufferedWriter(new FileWriter(file, true));
                    scores.newLine();
                    scores.write("client: " + clientID + " completed one iteration scores ++");
                    scores.flush();
                    scores.close();
                    scores = null;
                }
                System.gc();
                out.flush();
                out.close();
                notFound = false;

                clientSocket.close();
                communicating = false;
                return;
            } catch (SocketTimeoutException ex) {
                i++;
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex1) {
                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
                }
            } catch (UnknownHostException ex) {
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);

            } catch (ConnectException ex) {
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);


                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex1) {

                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
                }
            } catch (IOException ex) {
                ex.printStackTrace();

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ex1) {
                    Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex1);
                }
            } catch (IndexOutOfBoundsException ex) {
                System.err.println("Fire Escape Servers offline.");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
                i = 0;
            }
        }
    }

    /**
     * Returns the client ID
     * @return The client ID
     */
    public static String getID() {
        return clientID;
    }

    /**
     * Used as a benchmark for server logging purposes
     * @param timeToBlock The time to compute the benchmark in ms.
     * @return The calculation used as the benchmark
     */
    public static int benchmark(int timeToBlock) {

        double endTime = System.currentTimeMillis() + timeToBlock;
        double pi = 0;
        int k = 0;
        double b = -1;
        double a = -1;
        while (System.currentTimeMillis() < endTime) {
            a += 2;
            b = b * -1;
            pi += b * (4 / a);
            k++;
        }
        //ensures variable pi is used and calculated - else Java compiler ignores it.
        System.out.println(pi);
        //the integer of the number of calculations performed in the number of seconds specified in var timeToBlock.
        return k;
    }

    /**
     * Creates the server ID
     * @return The server ID
     */
    public static String createServerID() {


        String s = (String) JOptionPane.showInputDialog(
                new Frame(),
                "Please enter a user ID\n"
                + "This can be any sequence of letters or numbers\n"
                + "Enter \"null\" if you wish to use an automatically generated ID\n"
                + "However if another user/computer uses the same ID - your scores will be tallied together",
                "Input UID",
                JOptionPane.PLAIN_MESSAGE,
                null,
                null,
                "null");
        if (s.equalsIgnoreCase("null")) {
            s = null;
        }
        if ((s != null) && (s.length() > 0)) {
            clientID = s;
            return s;

        }
        try {
            String macAddress = null;
            //1.6 and up
            String sysinfo = System.getProperty("java.version");
            Logger.getLogger("FireEscapeLog").log(Level.INFO, sysinfo);
            InetAddress address = InetAddress.getLocalHost();
            NetworkInterface ni = NetworkInterface.getByInetAddress(address);
            byte[] mac = ni.getHardwareAddress();
            for (byte b : mac) {
                macAddress = macAddress + b;
            }
            MessageDigest m = MessageDigest.getInstance("MD5");
            m.update(macAddress.getBytes(), 0, macAddress.length());
            String MD5 = new BigInteger(1, m.digest()).toString(16);
            System.out.println(MD5);
            clientID = MD5;
            return MD5;
        } catch (UnknownHostException e) {
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, e);
            e.printStackTrace();
        } catch (SocketException e) {
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, e);
            e.printStackTrace();
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
            ex.printStackTrace();
        } catch (IOException e) {
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, e);
            e.printStackTrace();
        } catch (Exception e) {
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, e);
            e.printStackTrace();
        }
        System.err.println("ERROR: could not get MD5 sum");
        System.err.println("Generating random MD5");
        try {
            MessageDigest m = MessageDigest.getInstance("MD5");
            String random = Double.toString(Math.random());
            m.update(random.getBytes(), 0, random.length());
            String MD5 = new BigInteger(1, m.digest()).toString(16);
            System.out.println(MD5);
            clientID = MD5;
            return MD5;
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
        }
        System.err.println("Fatal error occured, random MD5 generation failed");
        return "FATALERRORNOMD5";
    }

    public static void checkOnlineStatus() {
        //TODO SET BACK TO FALSE LATER
        boolean gotStatus = true;
        while (gotStatus == false) {
            try {

                // get  server address
                URL kloudvine = new URL(onlineStatusServers);
                URLConnection kloudvineConnect = kloudvine.openConnection();
                kIn = new BufferedReader(new InputStreamReader(kloudvineConnect.getInputStream()));
                String inputLine = kIn.readLine();
                if (inputLine.equals("offline")) {
                    final JOptionPane offlineDialogPane = new JOptionPane(
                            "FireEscape is offline. Please try again later.",
                            JOptionPane.ERROR_MESSAGE);
                    try {
                        Thread.sleep(15000);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    System.exit(0);
                }
                kIn.close();
                gotStatus=true;
            } catch (MalformedURLException ex) {
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException ex1) {
                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex1);
                }
            } catch (IOException ex) {
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
                                try {
                    Thread.sleep(3000);
                } catch (InterruptedException ex1) {
                    Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex1);
                }
            }
        }
    }
}
